package org.skylight1.hrm;
import java.util.UUID;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
public class BluetoothLeService extends Service {
private final static String TAG = BluetoothLeService.class.getSimpleName();
private BluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
private String mBluetoothDeviceAddress;
private BluetoothGatt mBluetoothGatt;
private int mConnectionState = STATE_DISCONNECTED;
private Handler mTimerHandler = new Handler();
private boolean mTimerEnabled = false;
protected int retries;
private static final int STATE_DISCONNECTED = 0;
private static final int STATE_CONNECTING = 1;
private static final int STATE_CONNECTED = 2;
public final static String ACTION_GATT_CONNECTED =
"com.example.bluetooth.le.ACTION_GATT_CONNECTED";
public final static String ACTION_GATT_DISCONNECTED =
"com.example.bluetooth.le.ACTION_GATT_DISCONNECTED";
public final static String ACTION_GATT_SERVICES_DISCOVERED =
"com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";
public final static String ACTION_DATA_AVAILABLE =
"com.example.bluetooth.le.ACTION_DATA_AVAILABLE";
public final static String EXTRA_DATA =
"com.example.bluetooth.le.EXTRA_DATA";
public final static String RSSI_DATA =
"com.example.bluetooth.le.RSSI_DATA";
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
String intentAction;
if (newState == BluetoothProfile.STATE_CONNECTED) {
intentAction = ACTION_GATT_CONNECTED;
mConnectionState = STATE_CONNECTED;
broadcastUpdate(intentAction);
Log.i(TAG, "connected to GATT server");
if(mBluetoothGatt!=null) {
mBluetoothGatt.discoverServices();
}
// readRSSIContinually(true); // optional
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
intentAction = ACTION_GATT_DISCONNECTED;
mConnectionState = STATE_DISCONNECTED;
Log.i(TAG, "disconnected from GATT server");
broadcastUpdate(intentAction);
}
}
@Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status){
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(RSSI_DATA, rssi);
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
if(gatt.getServices().size()<4 && retries<5) {
retries++;
Log.w(TAG,"found only "+gatt.getServices().size()+" services, retrying discoverServices...");
try {Thread.sleep(50);} catch(InterruptedException ioe){}
gatt.discoverServices();
return;
}
startMonitoring(gatt, true);
broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}
private void startMonitoring(BluetoothGatt gatt, boolean value) {
if(gatt==null) {
return;
}
BluetoothGattService service = gatt.getService(BleServices.SVC_HEART_RATE);
BluetoothGattCharacteristic characteristic = null;
if(service==null) {
Log.i(TAG,"service is null, trying creating one and characteristic");
service = new BluetoothGattService(BleServices.SVC_HEART_RATE,0);
characteristic = new BluetoothGattCharacteristic(BleCharacteristics.CHAR_HEART_RATE_MEASUREMENT, 16, 0);
service.addCharacteristic(characteristic);
} else {
characteristic = service.getCharacteristic(BleCharacteristics.CHAR_HEART_RATE_MEASUREMENT);
if(characteristic==null) {
Log.i(TAG,"characteristic is null");
return;
}
}
setCharacteristicNotification(characteristic, value);
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
};
public void readRSSIContinually(final boolean enable) {
mTimerEnabled = enable;
if(mConnectionState != STATE_CONNECTED || mBluetoothGatt == null || mTimerEnabled == false) {
mTimerEnabled = false;
return;
}
mTimerHandler.postDelayed(new Runnable() {
@Override
public void run() {
if(mBluetoothGatt == null ||
mBluetoothAdapter == null ||
mConnectionState != STATE_CONNECTED) {
mTimerEnabled = false;
return;
}
try {
mBluetoothGatt.readRemoteRssi();
readRSSIContinually(mTimerEnabled);
} catch(Exception doe) {
Log.e(TAG,"failed to read RSSI: "+doe);
readRSSIContinually(false);
}
}
}, 2000);
}
private void broadcastUpdate(final String action) {
final Intent intent = new Intent(action);
sendBroadcast(intent);
}
private void broadcastUpdate(final String action,
final BluetoothGattCharacteristic characteristic) {
final Intent intent = new Intent(action);
// This is special handling for profiles.
// Data parsing is carried out as per profile specifications:
// http://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx
String result = BleProfiles.processReadCharacteristic(characteristic);
intent.putExtra(EXTRA_DATA, result);
sendBroadcast(intent);
}
private void broadcastUpdate(final String action, final int rssi) {
final Intent intent = new Intent(action);
intent.putExtra(EXTRA_DATA, rssi);
sendBroadcast(intent);
}
public class LocalBinder extends Binder {
public BluetoothLeService getService() {
return BluetoothLeService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
close();
return super.onUnbind(intent);
}
private final IBinder mBinder = new LocalBinder();
public boolean initialize() {
if (mBluetoothManager == null) {
mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
if (mBluetoothManager == null) {
return false;
}
}
mBluetoothAdapter = mBluetoothManager.getAdapter();
if (mBluetoothAdapter == null) {
return false;
}
return true;
}
public boolean connect(final String address) {
if (mBluetoothAdapter == null || address == null) {
Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
return false;
}
if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress) && mBluetoothGatt != null) {
Log.d(TAG, "using an existing mBluetoothGatt for connection");
if (mBluetoothGatt.connect()) {
mConnectionState = STATE_CONNECTING;
return true;
} else {
return false;
}
}
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
if (device == null) {
Log.w(TAG, "Device not found. Unable to connect.");
return false;
}
mConnectionState = STATE_CONNECTING;
Runnable task = new Runnable() {
public void run() {
mBluetoothGatt = device.connectGatt(BluetoothLeService.this, false, mGattCallback);
Log.d(TAG, "device.connectGatt()");
mBluetoothDeviceAddress = address;
}
};
// new Handler(Looper.getMainLooper()).post(task);
new Thread(task).start();
return true;
}
public void disconnect() {
if (mBluetoothAdapter == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
if(mBluetoothGatt != null) {
readRSSIContinually(false);
mBluetoothGatt.disconnect();
}
}
public void close() {
if (mBluetoothGatt == null) {
return;
}
mBluetoothGatt.close();
mBluetoothGatt = null;
}
public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
mBluetoothGatt.readCharacteristic(characteristic);
}
public void writeCharacteristic(BluetoothGattCharacteristic characteristic) {
mBluetoothGatt.writeCharacteristic(characteristic);
}
/**
* Enables or disables notification on a given characteristic.
*
* @param characteristic
* Characteristic to act on.
* @param enabled
* If true, enable notification. False otherwise.
*/
public boolean setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enabled) {
if (mBluetoothGatt == null || mConnectionState != STATE_CONNECTED) {
return false;
}
if (mBluetoothAdapter == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return false;
} else if (mBluetoothGatt == null) {
Log.w(TAG, "BluetoothGatt not available");
return false;
}
if (!mBluetoothGatt.setCharacteristicNotification(characteristic, enabled)) {
Log.w(TAG, "setCharacteristicNotification failed");
return false;
}
UUID descriptorUUID = BleProfiles.getdescriptor(characteristic);
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(descriptorUUID);
if (descriptor != null) {
if (enabled) {
if(!descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)) {
Log.e(TAG,"setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE) FAILED");
}
} else {
if(!descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)) {
Log.e(TAG,"setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE) FAILED");
}
}
boolean ok = mBluetoothGatt.writeDescriptor(descriptor);
if(!ok) {
Log.e(TAG,"bluetoothGatt.writeDescriptor "+descriptor.getUuid().toString()+" FAILED");
}
return ok;
} else {
return false;
}
}
}